package com.hulk.dryad.web.filter;


import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.hulk.dryad.common.component.R;
import com.hulk.dryad.common.constant.CommonConstants;
import com.hulk.dryad.common.constant.enums.BEC;
import com.hulk.dryad.common.exception.BaseRteException;
import com.hulk.dryad.common.exception.SecurityRteException;
import com.hulk.dryad.common.util.RequestUtils;
import com.hulk.dryad.common.util.SignUtil;
import com.hulk.dryad.common.util.WebUtils;
import com.hulk.dryad.manage.framework.tenant.TenantHierarchyUtil;
import com.hulk.dryad.manage.provider.cache.SignKeyCache;
import com.hulk.dryad.manage.security.component.PermitAllSecurityUrlProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * @author hulk
 */
@Slf4j
@Component
@EnableConfigurationProperties(PermitAllSecurityUrlProperties.class)
@RequiredArgsConstructor
public class SignatureFilter extends OncePerRequestFilter implements InitializingBean, Ordered {

	private final PermitAllSecurityUrlProperties permitAllSecurityUrlProperties;
    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    //认证TOKEN
    private static final String AUTHORIZATION = "Authorization";
    //时间戳 yyyyMMddHHmmss
    private static final String TIMESTAMP = "timestamp";
    //uri
    private static final String URI  = "uri";
    //临时流水号
    private static final String NONCE = "nonce";
    @Value("${dryad.signature.enabled:true}")
    private Boolean signatureEnable;
    //5分钟内数据有效
    @Value("${dryad.signature.attachment.timestamp:5}")
    private Integer attachTimestamp = 5;
    @Value("${dryad.signature.attachment.enabled:false}")
    private Boolean attachEnabled;
    private final SignKeyCache signKeyCache;


    @SuppressWarnings("deprecation")
    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException {
        try {
            log.debug("ContentType:[{}],HttpMethod:[{}]", request.getContentType(), request.getMethod());
            String path = request.getRequestURI();
            boolean flag = !permitAllSecurityUrlProperties.getPermitAll().stream().anyMatch(url -> PATH_MATCHER.match(url, path));
            if (!flag) {
                chain.doFilter(request, resp);
            } else {
                if (flag && attachEnabled) {
                    String cts = this.getTimestamp(request);
                    checkTimestamp(cts);
                }
                if (RequestUtils.isContainBody(request) && StringUtils.equalsAnyIgnoreCase(request.getContentType(),
                        MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE)) {
                    if (flag && signatureEnable) {
                        Map attachMap = attachEnabled ? this.getAttachSignMap(request) : Maps.newHashMap();
                        chain.doFilter(new SignatureServletRequestWrapper(request, signKeyCache, attachMap), resp);
                    } else {
                        chain.doFilter(request, resp);
                    }
                } else if (RequestUtils.isGet(request) || RequestUtils.isDelete(request)) {
                    Map param = HttpUtil.decodeParamMap(request.getRequestURI(), StandardCharsets.UTF_8);
                    if (attachEnabled) {
                        param.putAll(this.getAttachSignMap(request));
                    }
                    if (flag && signatureEnable && param.containsKey(SignUtil.SIGNATURE)) {
                        String signature = String.valueOf(param.get(SignUtil.SIGNATURE));
                        boolean signFlag = SignUtil.verify(param, signature, signKeyCache.getSignKey());
                        if (!signFlag) {
                            throw new SecurityRteException(BEC.E_2005);
                        }
                    }
                    chain.doFilter(request, resp);
                }
            }
        } catch (BaseRteException e) {
            WebUtils.renderJson(resp, R.failed(e.getErrCode(), e.getErrDesc()));
        }
//        catch (Exception e) {
//            WebUtils.renderJson(resp, R.failed(BEC.E_9999.getErrCode(), Throwables.getStackTraceAsString(e)));
//        }
    }


    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 1000;
    }


    //验证时间戳有效期
    private void checkTimestamp(String cts) {
        if (!attachEnabled) {
            return;
        }
        LocalDateTime timestamp = LocalDateTimeUtil.parse(cts, DatePattern.PURE_DATETIME_PATTERN);
        LocalDateTime ostime = LocalDateTimeUtil.now();
        if (!timestamp.isBefore(ostime.plusMinutes(attachTimestamp))) {
            throw new SecurityRteException(BEC.E_2019);
        }
    }

    /**
     * 签名附加信息
     *
     * @return
     */
    private Map getAttachSignMap(HttpServletRequest request) {
        Map m = Maps.newHashMap();
        m.put(CommonConstants.TENANT_ID, this.getTenantId());
        m.put(AUTHORIZATION, this.getToken(request));
        m.put(URI, request.getRequestURI());
        m.put(TIMESTAMP, this.getTimestamp(request));
        String nonce = this.getNonce(request);
        if (StrUtil.isNotBlank(nonce)) {
            m.put(NONCE, nonce);
        }
        return m;
    }

    /**
     * 获取timestamp
     *
     * @return
     */
    private String getTimestamp(HttpServletRequest request) {
        String timestamp = request.getHeader(TIMESTAMP);
        timestamp = StrUtil.isEmpty(timestamp) ? request.getParameter(TIMESTAMP) : timestamp;
        if (StrUtil.isBlank(timestamp)) {
            throw new SecurityRteException(BEC.E_2026);
        }
        return timestamp;
    }

    /**
     * 获取TOKEN
     *
     * @return
     */
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(AUTHORIZATION);
        return token;
    }

    /**
     * 获取租户ID
     *
     * @return
     */
    private String getTenantId() {
        return TenantHierarchyUtil.getTenantId();
    }

    /**
     * 获取nonce
     *
     * @return
     */
    @Deprecated
    private String getNonce(HttpServletRequest request) {
        String nonce = request.getHeader(NONCE);
        nonce = StrUtil.isEmpty(nonce) ? request.getParameter(NONCE) : nonce;
        if (StrUtil.isBlank(nonce)) {
            //throw new SecurityRteException(BEC.E_2027);
        }
        return nonce;
    }

}
